package dk.simonvogensen.itodo.client.controller;

import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.KeyboardListener;
import dk.simonvogensen.itodo.client.ITodoGWTUtils;
import dk.simonvogensen.itodo.client.backend.Backend;
import dk.simonvogensen.itodo.client.backend.ITodoBackend;
import dk.simonvogensen.itodo.client.model.Project;
import dk.simonvogensen.itodo.client.model.ProjectId;
import dk.simonvogensen.itodo.client.model.TransportType;
import dk.simonvogensen.itodo.client.view.ITodoView;
import dk.simonvogensen.itodo.shared.model.Config;
import dk.simonvogensen.itodo.shared.model.TagLine;
import dk.simonvogensen.itodo.shared.model.TodoItem;

import java.util.*;

public class Controller {
    private ITodoView view;
    private Backend backend;

    private List<TodoItem> itemList = new ArrayList<TodoItem>();
    private TodoItem selectedItem;
    private ITodoGWTUtils gwtUtils;
    private static final int CHROME_MODIFIER_ALT = 12;
    private Config config;
    private static final int SUBVIEW_SIZE = 35;

    public Controller(final ITodoView view, ITodoBackend backend, ITodoGWTUtils gwtUtils) {
        this.view = view;
        view.setController(this);
        this.backend = (Backend) backend;
        this.gwtUtils = gwtUtils;
    }

    public void init() {
        backend.init();

        this.backend.getLatestTodoItem(new AsyncCallback<TodoItem>() {
            public void onFailure(Throwable caught) {
                view.showAlert(caught.getMessage());
            }

            public void onSuccess(TodoItem todoItem) {
                config = todoItem.getConfig();
                itemList.add(0,todoItem);
                selectedItem = itemList.get(0);
                view.updateProjectIdsAndTransportTypes();
                view.updateView();
                updateButtonStates(false);
                fetchTodoItems(1, 100);
            }
        });

        gwtUtils.initUpdateTimer(new Command() {
            public void execute() {
                updateTodoItem(selectedItem);
            }
        });
    }

    private void fetchTodoItems(final int startIndex, final int endIndex) {
        this.backend.getTodoItemList(startIndex, endIndex, new AsyncCallback<List<TodoItem>>() {
            public void onFailure(Throwable caught) {
                view.showAlert(caught.getMessage());
            }

            public void onSuccess(List<TodoItem> todoItemList) {

                Controller.this.itemList.addAll(todoItemList);

                int resultSize = todoItemList.size();
                int fetchSize = endIndex - startIndex;
                if(fetchSize == resultSize) { // maybe theres more
                    fetchTodoItems(endIndex, endIndex+fetchSize);
                } else {
                    view.updateView();
                    updateButtonStates(false);
                }
            }
        });
    }

    public void selectPreviousTodo() {
        int index = itemList.indexOf(selectedItem);
        if (index < itemList.size() - 1) {
            updateTodoItem(selectedItem);
            selectedItem = itemList.get(index + 1);
            view.updateView();
        }
        updateButtonStates(false);
        System.out.println("Previous: index selected: " + itemList.indexOf(selectedItem));
    }

    public void selectNextTodo() {
        int index = itemList.indexOf(selectedItem);
        if (index > 0) {
            updateTodoItem(selectedItem);
            selectedItem = itemList.get(index - 1);
            view.updateView();
        }
        updateButtonStates(false);
        System.out.println("Next: index selected: " + itemList.indexOf(selectedItem));
    }

    public void selectTodo(TodoItem item) {
        updateTodoItem(selectedItem);
        selectedItem = item;
        view.updateView();
        updateButtonStates(false);
        System.out.println("Select: index selected: " + itemList.indexOf(selectedItem));
    }

    public TodoItem getSelectedTodoItem() {
        return selectedItem;
    }

    public List<TodoItem> getTodoItemList() {
        return itemList;
    }

    public List<TodoItem> getVisibleTodoItemList() {
        List<TodoItem> visibleList = new ArrayList<TodoItem>();

        // add up to 35 items before selected
        int i2 = Math.max(0, getTodoItemList().indexOf(getSelectedTodoItem()));
        int i1 = Math.max(i2- SUBVIEW_SIZE, 0);
        visibleList.addAll(subList(getTodoItemList(), i1, i2));

        // from selecteditem goto max next 35 items
        int i3 = Math.min(i2+ SUBVIEW_SIZE, getTodoItemList().size());
        visibleList.addAll(subList(getTodoItemList(), i2, i3));

        return visibleList;
    }

    //TODO generify this to T
    // GWT dont support subList - so we implement it ourselves
    private List<TodoItem> subList(List<TodoItem> list, int i1, int i2) {
        if (list == null) return null;
        List<TodoItem> subList = new ArrayList<TodoItem>();
        for (int i = i1; i < i2; i++) {
            subList.add(list.get(i));
        }
        return subList;
    }

    public void deleteTodoItem() {
        final int index = itemList.indexOf(selectedItem);
        backend.deleteTodo(selectedItem, new AsyncCallback() {
            public void onFailure(Throwable caught) {
                view.showAlert(caught.getMessage());
            }

            public void onSuccess(Object result) {
                itemList.remove(selectedItem);
                if (index < itemList.size())
                    selectedItem = itemList.get(index);
                else if (index - 1 >= 0 && index - 1 < itemList.size())
                    selectedItem = itemList.get(index - 1);
                else
                    selectedItem = null;
                view.updateView();
                updateButtonStates(false);
            }
        });
    }

    public void createNewTodo() {
        updateTodoItem(selectedItem);
        backend.createTodo(new AsyncCallback() {
            public void onFailure(Throwable caught) {
                view.showAlert(caught.getMessage());
            }

            public void onSuccess(Object result) {
                String id = (String) result;

                TodoItem item = null;
                int index = 0;
                if (!itemList.isEmpty()) {
                    item = itemList.get(0);
                    index = item.getIndex() + 1;
                }
                selectedItem = new TodoItem(config, item, id, null, index);
                selectedItem.setModified(true);
                itemList.add(0, selectedItem);
                view.updateView();
                updateButtonStates(false);
            }
        });
    }

    public void addTime(ProjectId pi) {
        getSelectedTodoItem().addTime(pi, new Date());
        updateTodoItem(getSelectedTodoItem());
        view.refreshAxedArea();
        updateTodo(getSelectedTodoItem());
    }

    public void updateTodoItem(final TodoItem item) {
        if (item != null && item.isModified()) {
            gwtUtils.cancelUpdateTimer();
            updateButtonStates(false);
            //TODO is this always valid? could I end up saving text from one item to another?
            // if its a problem add a verifying id in view and compare before update
            String text = view.getSelectedTodoItemText();
            item.setText(text);
            updateTodo(item);
        }
    }

    private void updateTodo(final TodoItem item) {
        // make item clone that exclude references..
        backend.updateTodo(new TodoItem(item.getId(), item.getIndex(), item.getText()), new AsyncCallback() {
            public void onFailure(Throwable caught) {
                view.showAlert(caught.getMessage());
            }

            public void onSuccess(Object result) {
                item.setModified(false);
                view.updateView();
                view.showAlert("Todo saved");
            }
        });
    }

    private void updateButtonStates(boolean enableSaveButton) {
        boolean enableDeleteButton = !itemList.isEmpty();
        boolean enableNextButton = !itemList.isEmpty() && itemList.indexOf(selectedItem) > 0;
        boolean enablePreviousButton = !itemList.isEmpty() && itemList.indexOf(selectedItem) < itemList.size() - 1;
        view.updateButtonStates(enableNextButton, enablePreviousButton, enableSaveButton, enableDeleteButton);
    }

    public boolean onGlobalKeyDown(char keyCode, int modifiers) {
        if (keyCode == 'E') {
            if (getSelectedTodoItem() != null) {
                view.setAxedAreaFocus(true);
                return false;
            }
        } else {
            return onKeyDown(keyCode, modifiers);
        }
        return true;
    }

    private boolean onKeyDown(char keyCode, int modifiers) {
        if (modifiers == CHROME_MODIFIER_ALT || modifiers == KeyboardListener.MODIFIER_ALT) {
            if (keyCode == 'N') {
                selectNextTodo();
                return true;
            } else if (keyCode == 'P') {
                selectPreviousTodo();
                return true;
            } else if (keyCode == 'I') {
                createNewTodo();
                return true;
            } else if (keyCode == 'S') {
                updateTodoItem(getSelectedTodoItem());
                return true;
            } else if (keyCode == 'D') {
                deleteTodoItem();
                return true;
            } else if (keyCode == 'W') {
                openWorktimeDialog();
                return true;
//            } else if(keyCode == 'A') {
//                addTime(pi);
//                return true;
//            } else if (keyCode == 'T') {
//                setTagsFocus();
//                return true;
            }
        } else if (keyCode == KeyboardListener.KEY_ESCAPE) {
            if (view.hasOpenDialogs()) view.closeDialogs();
            else view.unfocus();
            return true;
        }
        return false;
    }

    public boolean onAxedAreaKeyDown(char keyCode, int modifiers) {
        return onKeyDown(keyCode, modifiers);
    }

    public boolean onAxedAreaKeyUp(char keyCode, int modifiers) {
        boolean preventDefault;
        if (
                modifiers != KeyboardListener.MODIFIER_ALT &&
                        modifiers != KeyboardListener.MODIFIER_CTRL &&
                        keyCode != KeyboardListener.KEY_DOWN &&
                        keyCode != KeyboardListener.KEY_END &&
                        keyCode != KeyboardListener.KEY_ESCAPE &&
                        keyCode != KeyboardListener.KEY_HOME &&
                        keyCode != KeyboardListener.KEY_LEFT &&
                        keyCode != KeyboardListener.KEY_PAGEDOWN &&
                        keyCode != KeyboardListener.KEY_PAGEUP &&
                        keyCode != KeyboardListener.KEY_RIGHT &&
                        keyCode != KeyboardListener.KEY_UP
                ) {
            selectedItem.setModified(true);
            gwtUtils.refreshUpdateTimer();
            updateButtonStates(true);
            preventDefault = true;
        } else {
            preventDefault = false;
        }

//        System.out.println("onAxedKeyUp - "+keyCode+" "+modifiers);
//        view.showAlert("axedkeyup:"+keyCode+" modifier:"+modifiers+" prevent:"+preventDefault);

        return preventDefault;
    }

/*
	public void sync() {
		view.showAlert("Synchronizing..");

		// send list of todoitems on client (id,clientversion, origversion)
		// include items the server should update
		// server sends back list of changes (id,cmd elements)
		// states IN_SYNC, MERGE, CREATE_ON_SERVER, CREATE_ON_CLIENT, UPDATE_ON_SERVER, UPDATE_ON_CLIENT
		List todoItemList = model.getTodoItemList();
		String ids = "&ids=";
		String originalVersions = "&origvers=";
		String clientVersions = "&clientvers=";
		for (Iterator it = todoItemList.iterator(); it.hasNext();) {
			TodoItem todoItem = (TodoItem) it.next();
			ids += todoItem.getId();
			originalVersions += todoItem.getServerVersion();
			clientVersions += todoItem.getClientVersion();
			if(it.hasNext()) {
				ids += ",";
				originalVersions += ",";
				clientVersions += ",";
			}
		}

		String requestString = REQUEST_URL+"?cmd=sync"+REQUEST_PWD+ids+originalVersions+clientVersions;
		model.sendRequest(requestString, new AsyncCallback(){
			public void onFailure(Throwable caught) {
				view.showAlert(caught.getMessage());
			}
			public void onSuccess(Object result) {
				doSync((List) result);
			}
		});
	}

	protected void doSync(List syncItemList) {
		for (Iterator it = syncItemList.iterator(); it.hasNext();) {
			SyncItem syncItem = (SyncItem) it.next();
            if(SyncItem.MERGE.equals(syncItem.state)) {
//				view.showAlert("Item "+getTodoItem(syncItem.id).getHeaderText()+" has mergeconflicts! - sync stopped.");
				return;
            } 
            else if(SyncItem.CREATE_ON_SERVER.equals(syncItem.state)) {
            	doSyncCOS(syncItem);
            } 
            else if(SyncItem.CREATE_ON_CLIENT.equals(syncItem.state)) {
            	doSyncCOC(syncItem);
            } 
            else if(SyncItem.UPDATE_ON_SERVER.equals(syncItem.state)) {
            	doSyncUOS(syncItem);
            } 
            else if(SyncItem.UPDATE_ON_CLIENT.equals(syncItem.state)) {
            	doSyncUOC(syncItem);
			}
		}
	}

	protected void doSyncCOC(SyncItem syncItem) {
		String id = "&id="+syncItem.id;
		String requestString = REQUEST_URL+"?cmd=get"+REQUEST_PWD+id;
		model.sendRequest(requestString, new AsyncCallback(){
			public void onFailure(Throwable caught) {
				view.showAlert(caught.getMessage());
			}
			public void onSuccess(Object result) {
				TodoItem item = (TodoItem) result;
				TodoItem createdItem = model.createNewTodo();
				model.updateTodoItem(createdItem.getId(), item);
			}
		});
	}
	
	void doSyncUOC(SyncItem syncItem) {
		String id = "&id="+syncItem.id;
		String requestString = REQUEST_URL+"?cmd=get"+REQUEST_PWD+id;
		model.sendRequest(requestString, new AsyncCallback(){
			public void onFailure(Throwable caught) {
				view.showAlert(caught.getMessage());
			}
			public void onSuccess(Object result) {
				TodoItem serverItem = (TodoItem) result;
				TodoItem item = model.getTodoItem(serverItem.getId());
				model.updateTodoItem(item.getId(), serverItem);
			}
		});
	}

	protected void doSyncCOS(SyncItem syncItem) {
		TodoItem item = model.getTodoItem(syncItem.id);
		item.setId(syncItem.serverId);
		model.updateTodoItem(syncItem.id, item);
		String text = "&text="+model.encodeComponent(item.getText());
		String headertext = "&headertext="+model.encodeComponent(item.getHeaderText());
		String requestString = REQUEST_URL+"?cmd=create"+REQUEST_PWD+text+headertext;
		model.sendRequest(requestString, new AsyncCallback(){
			public void onFailure(Throwable caught) {
				view.showAlert(caught.getMessage());
			}
			public void onSuccess(Object result) {
			}
		});
	}
	
	void doSyncUOS(SyncItem syncItem) {
		TodoItem item = model.getTodoItem(syncItem.id);
		String id = "&id="+item.getId();
		String text = "&text="+model.encodeComponent(item.getText());
		String headertext = "&headertext="+model.encodeComponent(item.getHeaderText());
		String requestString = REQUEST_URL+"?cmd=update"+REQUEST_PWD+id+text+headertext;
		model.sendRequest(requestString, new AsyncCallback(){
			public void onFailure(Throwable caught) {
				view.showAlert(caught.getMessage());
			}
			public void onSuccess(Object result) {
			}
		});
	}
*/

    public Map<TransportType, Integer> getTransportTypeMap() {
        Map<TransportType, Integer> transportTypeMap = new HashMap<TransportType, Integer>();
        int month = getSelectedTodoItem().getDate().getMonth();
        TodoItem todoItem = getSelectedTodoItem();
        do {
            if(!transportTypeMap.containsKey(todoItem.getTransportType())) {
                 transportTypeMap.put(todoItem.getTransportType(), 0);
            }
            transportTypeMap.put(todoItem.getTransportType(), transportTypeMap.get(todoItem.getTransportType()) + 1);
            todoItem = todoItem.getPrevItem();
        } while (todoItem != null && todoItem.getDate() != null && todoItem.getDate().getMonth() == month);

        return transportTypeMap;
    }

    public List<String[]> getWorktimeList() {
        List<String[]> worktimeList = new ArrayList<String[]>();
        double[] ialtValues = new double[2 + config.getProjectIdList().size()];
        for (int i = 1; i <= 31; i++) {
            TodoItem todoItem = getSelectedTodoItem();
            if(todoItem != null) {
                TodoItem selectedTodoItem = null;
                int month = todoItem.getDate().getMonth();
                int worktimeInMinutes = 0;
                do {
                    if (todoItem.getDate().getDate() == i) {
                        worktimeInMinutes = todoItem.getWorktimeInMinutes();
                        selectedTodoItem = todoItem;
                        break;
                    }
                    todoItem = todoItem.getPrevItem();
                } while (todoItem != null && todoItem.getDate() != null && todoItem.getDate().getMonth() == month);
                String[] strings = new String[2 + config.getProjectIdList().size()];
                String date = i + "/" + (month + 1);
                strings[0] = date;

                if (selectedTodoItem != null) {
                    double worktime = (worktimeInMinutes + 0.0) / 60;
                    double worktimeRounded = roundWorktime(worktime);
                    strings[1] = worktimeRounded > 0.0 ? "" + worktimeRounded : "";
                    ialtValues[1] += worktimeRounded;
                    int j = 2;
                    for (ProjectId projectId : config.getProjectIdList()) {
                        Project project = selectedTodoItem.getProject(projectId);
                        if (project != null) {
                            worktime = (project.getWorktimeInMinutes() + 0.0) / 60;
                            worktimeRounded = roundWorktime(worktime);
                            strings[j] = "" + worktimeRounded;
                            ialtValues[j] += worktimeRounded;
                        } else {
                            strings[j] = "";
                        }
                        j++;
                    }
                }
                worktimeList.add(strings);
            }
        }
        //ialt
        String[] ialtStrings = new String[2 + config.getProjectIdList().size()];
        ialtStrings[0] = "ialt";
        ialtStrings[1] = "" + ialtValues[1];
        for (int i = 0; i < config.getProjectIdList().size(); i++) {
            ialtStrings[i + 2] = ialtValues[i + 2] > 0.0 ? "" + ialtValues[i + 2] : "";
        }
        worktimeList.add(ialtStrings);
        return worktimeList;
    }

    public void openWorktimeDialog() {
        view.openWorktimeDialog();
    }

    public void openConfigDialog() {
        view.openConfigDialog();
    }

    public void changeTransportType(TransportType transportType) {
        updateTodoItem(getSelectedTodoItem());
        getSelectedTodoItem().changeTransportType(transportType);
        view.refreshAxedArea();
        updateTodo(getSelectedTodoItem());
    }

    public String getXml() {
        TodoItem item = getSelectedTodoItem();
        String xml = "<?xml version=\"1.1\" encoding=\"UTF-8\" ?>\n";
        if(item != null) {
            int m = item.getDate().getMonth() + 1;
            int y = item.getDate().getYear() + 1900;
            String yearmonth = "" + y + (m < 10 ? "0" + m : "" + m);
            xml += "<TimeRegistrations employeeID=\"" + config.getEmployeeId() + "\" period=\"" + yearmonth + "\" xmlns=\"http://www.lakeside.dk/timereg\">\n";


            for (int i = 1; i <= 31; i++) {
                TodoItem selectedTodoItem = null;
                TodoItem todoItem = item;
                int month = todoItem.getDate().getMonth();
                do {
                    if (todoItem.getDate().getDate() == i) {
                        selectedTodoItem = todoItem;
                        break;
                    }
                    todoItem = todoItem.getPrevItem();
                } while (todoItem != null && todoItem.getDate() != null && todoItem.getDate().getMonth() == month);
                if (selectedTodoItem != null) {
                    String day = i < 10 ? "0" + i : "" + i;
                    //TODO SBV kørsel is hardcoded right now
                    String monthString = month + 1 < 10 ? "0" + (month + 1) : "" + (month + 1);
                    if (selectedTodoItem.getTransportType() != null && selectedTodoItem.getTransportType().getName().equals("bil")) {
                        xml += "  <TimeReg date=\"" + y + "-" + monthString + "-" + day + "+01:00\" units=\"190\" type=\"km\" caseid=\"NORDEA-NETL\" comment=\"\"/>\n";
                    }
                    for (ProjectId projectId : config.getProjectIdList()) {
                        Project project = selectedTodoItem.getProject(projectId);
                        if (project != null && project.getWorktimeInMinutes() != 0) {
                            double worktime = (project.getWorktimeInMinutes() + 0.0) / 60;
                            double worktimeRounded = roundWorktime(worktime);
                            xml += "  <TimeReg date=\"" + y + "-" + monthString + "-" + day + "+01:00\" units=\"" + worktimeRounded + "\" type=\"workhours\" caseid=\"" + project.getProjectId().getName() + "\" comment=\"\"/>\n";
                        }
                    }
                }
            }
            xml += "</TimeRegistrations>";
        }
        return xml;
    }

    private double roundWorktime(double worktime) {
        double worktimeRounded = worktime;
        double decimals = worktime - Math.floor(worktime);
        if (decimals < 0.25) worktimeRounded = Math.floor(worktime);
        else if (decimals >= 0.25 && decimals < 0.75) worktimeRounded = Math.floor(worktime) + 0.5;
        else if (decimals >= 0.75) worktimeRounded = Math.floor(worktime) + 1;
        return worktimeRounded;
    }

    public Config getConfig() {
        return config;
    }

    public void updateConfig(AsyncCallback async) {
        backend.updateConfig(config, async);
    }

    public void updateConfig() {
        updateConfig(new AsyncCallback() {
            public void onFailure(Throwable caught) {
                view.showAlert(caught.getMessage());
            }

            public void onSuccess(Object result) {
                config.setId((String) result);
                view.updateProjectIdsAndTransportTypes();
                view.showAlert("Config updated..");
            }
        });
    }

    public void setLogItemDone(TagLine tagLine) {
        tagLine.setDone();
        selectedItem = tagLine.getTag().getTodoItem();
        updateTodo(selectedItem);
//        view.updateView();
//        updateButtonStates(false);
    }

    public String exportData() {
        List<TodoItem> todoItemList = getTodoItemList();
        StringBuffer sb = new StringBuffer();
        sb.append("<config>").append("<employeeid>").append(config.getEmployeeId()).append("</employeeid>")
                .append("<projectids>").append(config.getProjectIds()).append("</projectids>")
                .append("<transporttypes>").append(config.getTransportTypes()).append("</transporttypes>").append("</config>\n");
        for (int i = todoItemList.size()-1; i >= 0; i--) {
            TodoItem todoItem = todoItemList.get(i);
            sb.append("<todoitem>").append(todoItem.getText()).append("</todoitem>\n");
        }
        return sb.toString();
    }

    public void importData(final String importString) {
//        Pattern p = Pattern.compile("<config><employeeid>(.*)</employeeid><projectids>(.*)</projectids><transporttypes>(.*)</transporttypes></config>");
//        Matcher m = p.matcher(importString);
//        m.find();

        config.setEmployeeId(importString.substring(importString.indexOf("<employeeid>")+12, importString.indexOf("</employeeid>")));
        config.setProjectIds(importString.substring(importString.indexOf("<projectids>")+12, importString.indexOf("</projectids>")));
        config.setTransportTypes(importString.substring(importString.indexOf("<transporttypes>")+16, importString.indexOf("</transporttypes>")));
        updateConfig(new AsyncCallback() {
            public void onFailure(Throwable caught) {
                view.showAlert(caught.getMessage());
            }

            public void onSuccess(Object result) {
                config.setId((String) result);
                view.updateProjectIdsAndTransportTypes();
                view.showAlert("Config updated..");

                backend.importData(importString, new AsyncCallback<Void>() {
                    public void onFailure(Throwable caught) {
                        view.showAlert(caught.getMessage());
                    }

                    public void onSuccess(Void result) {
                        view.showAlert("Data imported.. Reload app");

//                        Controller.this.backend.getTodoItemList(startIndex, fetchSize, new AsyncCallback<List<TodoItem>>() {
//                            public void onFailure(Throwable caught) {
//                                view.showAlert(caught.getMessage());
//                            }
//
//                            public void onSuccess(List<TodoItem> todoItemList) {
//                                if(!todoItemList.isEmpty()) {
//                                    Controller.this.itemList.clear();
//                                    Controller.this.itemList.addAll(todoItemList);
//                                    selectTodo(todoItemList.get(0));
//                                }
//                                view.updateView();
//                                updateButtonStates(false);
//                            }
//                        });
                    }
                });
            }
        });
    }
}
